package com.siashan.toolkit.crypt.asymmetric;

import com.siashan.toolkit.crypt.CryptException;
import com.siashan.toolkit.crypt.EncodeType;
import com.siashan.toolkit.crypt.KeyUtil;
import com.siashan.toolkit.crypt.util.BCUtil;
import com.siashan.toolkit.crypt.util.ECKeyUtil;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ParametersWithID;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.DSAEncoding;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.crypto.signers.StandardDSAEncoding;

/**
 * 国密SM2算法实现，基于BC库<br>
 * SM2算法只支持公钥加密，私钥解密<br>
 * 参考：https://blog.csdn.net/pridas/article/details/86118774
 *
 * @author siashan
 * @since 1.0.7
 */
public class SM2 {

    /**
     * 算法EC
     */
    private static final String ALGORITHM_SM2 = "SM2";

    /**
     * SM2默认曲线
     */
    public static final String SM2_CURVE_NAME = "sm2p256v1";

    // --------------------------------------------------------------------------------- Encrypt


    /**
     * 加密，SM2非对称加密的结果由C1,C2,C3三部分组成，其中：
     *
     * <pre>
     * C1 生成随机数的计算出的椭圆曲线点
     * C2 密文数据
     * C3 SM3的摘要值
     * </pre>
     *
     * @param data             被加密的bytes
     * @param pubKeyParameters 公钥参数
     * @return 加密后的bytes
     * @throws CryptException 异常
     */
    public static byte[] encrypt(byte[] data, CipherParameters pubKeyParameters) throws CryptException {
        final SM2Engine engine = getEngine();
        try {
            engine.init(true, pubKeyParameters);
            return engine.processBlock(data, 0, data.length);
        } catch (InvalidCipherTextException e) {
            throw new CryptException(e);
        }
    }


    /**
     * 加密，SM2非对称加密的结果由C1,C2,C3三部分组成，其中：
     *
     * @param data             被加密的bytes
     * @param publicKey 公钥参数
     * @return 加密后的bytes
     * @throws CryptException 加密异常
     */
    public static byte[] encrypt(byte[] data, byte[] publicKey) {
        return encrypt(data, ECKeyUtil.toSm2PublicParams(publicKey));
    }

    /**
     * 加密，SM2非对称加密的结果由C1,C2,C3三部分组成，其中：
     *
     * @param data             被加密的bytes
     * @param publicKeyPointX 公钥X
     * @param publicKeyPointY 公钥Y
     * @return 加密后的bytes
     * @throws CryptException 加密异常
     */
    public static byte[] encrypt(byte[] data, byte[] publicKeyPointX, byte[] publicKeyPointY) {
        return encrypt(data, BCUtil.toSm2Params(publicKeyPointX, publicKeyPointY));
    }

    /**
     * 加密，SM2非对称加密的结果由C1,C2,C3三部分组成，其中：
     *
     * @param data             被加密的bytes
     * @param publicKeyPointXHex 公钥X
     * @param publicKeyPointYHex 公钥Y
     * @return 加密后的bytes
     * @throws CryptException 加密异常
     */
    public static byte[] encrypt(byte[] data, String publicKeyPointXHex, String publicKeyPointYHex) {
        return encrypt(data, BCUtil.toSm2Params(publicKeyPointXHex, publicKeyPointYHex));
    }


    /**
     * 加密，SM2非对称加密的结果由C1,C2,C3三部分组成，其中：
     *
     * @param data             被加密的bytes
     * @param publicKey 公钥参数
     * @return 加密后的bytes
     * @throws CryptException 加密异常
     */
    public static byte[] encrypt(byte[] data, String publicKey) {
        return encrypt(data, KeyUtil.decodeKey(publicKey, EncodeType.HEX));
    }

    /**
     * 加密，SM2非对称加密的结果由C1,C2,C3三部分组成，其中：
     *
     * @param data             被加密的bytes
     * @param publicKey 公钥参数
     * @return 加密后的bytes
     * @throws CryptException 加密异常
     */
    public static byte[] encrypt(byte[] data, String publicKey, EncodeType encodeType) {
        return encrypt(data, KeyUtil.decodeKey(publicKey, encodeType));
    }

    // --------------------------------------------------------------------------------- Decrypt


    /**
     * 解密
     *
     * @param data    SM2密文，实际包含三部分：ECC公钥、真正的密文、公钥和原文的SM3-HASH值
     * @param privateKeyParameters 私钥参数
     * @return 加密后的bytes
     * @throws CryptException 异常
     */
    public static byte[] decrypt(byte[] data, CipherParameters privateKeyParameters) throws CryptException {
        final SM2Engine engine = getEngine();
        try {
            engine.init(false, privateKeyParameters);
            return engine.processBlock(data, 0, data.length);
        } catch (InvalidCipherTextException e) {
            throw new CryptException(e);
        }
    }

    /**
     * 解密
     *
     * @param data    SM2密文，实际包含三部分：ECC公钥、真正的密文、公钥和原文的SM3-HASH值
     * @param privateKey 私钥
     * @return 解密后的bytes
     * @throws CryptException 异常
     */
    public static byte[] decrypt(byte[] data, byte[] privateKey) throws CryptException {
        return decrypt(data, ECKeyUtil.toSm2PrivateParams(privateKey));
    }
    // --------------------------------------------------------------------------------- Sign and Verify



    /**
     * 用私钥对信息生成数字签名
     *
     * @param data 加密数据
     * @param privateKey 私钥
     * @return 签名
     */
    public byte[] sign(byte[] data, byte[] privateKey) {
        return sign(data, privateKey, null);
    }



    /**
     * 用私钥对信息生成数字签名
     *
     * @param data 被签名的数据数据
     * @param privateKey 私钥
     * @param id   可以为null，若为null，则默认withId为字节数组:"1234567812345678".getBytes()
     * @return 签名
     */
    public byte[] sign(byte[] data, byte[] privateKey, byte[] id) {
        final SM2Signer signer = getSigner();
        try {
            CipherParameters param = new ParametersWithRandom(ECKeyUtil.toSm2PrivateParams(privateKey));
            if (id != null) {
                param = new ParametersWithID(param, id);
            }
            signer.init(true, param);
            signer.update(data, 0, data.length);
            return signer.generateSignature();
        } catch (org.bouncycastle.crypto.CryptoException e) {
            throw new CryptException(e);
        }
    }



    /**
     * 用公钥检验数字签名的合法性
     *
     * @param data 签名后的数据
     * @param sign 签名
     * @param publicKey 公钥
     * @return 是否验证通过
     */
    public boolean verify(byte[] data, byte[] sign,byte[] publicKey) {
        return verify(data, sign, publicKey,null);
    }



    /**
     * 用公钥检验数字签名的合法性
     *
     * @param data 数据签名后的数据
     * @param sign 签名
     * @param id   可以为null，若为null，则默认withId为字节数组:"1234567812345678".getBytes()
     * @return 是否验证通过
     */
    public boolean verify(byte[] data, byte[] sign,byte[] publicKey, byte[] id) {
        final SM2Signer signer = getSigner();
        CipherParameters param = ECKeyUtil.toSm2PublicParams(publicKey);
        if (id != null) {
            param = new ParametersWithID(param, id);
        }
        signer.init(false, param);
        signer.update(data, 0, data.length);
        return signer.verifySignature(sign);
    }

    // ------------------------------------------------------------------------------------------------------------------------- Private method start


    /**
     * 获取{@link SM2Engine}，此对象为懒加载模式
     *
     * @return {@link SM2Engine}
     */
    private static SM2Engine getEngine() {
        Digest digest = new SM3Digest();
        SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;
        return new SM2Engine(digest, mode);
    }

    /**
     * 获取{@link SM2Signer}，此对象为懒加载模式
     *
     * @return {@link SM2Signer}
     */
    private static SM2Signer getSigner() {
        DSAEncoding encoding = StandardDSAEncoding.INSTANCE;
        Digest digest = new SM3Digest();
        return new SM2Signer(encoding, digest);
    }
    // ------------------------------------------------------------------------------------------------------------------------- Private method end
}
